#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>

#include <cmath>

#include <curses.h>

#include <arpa/inet.h>

#include "socket.h"
#include "common.h"

namespace robot {
	const double eps = 1e-3;

	template<class T> inline T abs(T x) {
		return (x >= 0) ? x : -x;
	}

	const int energy_max_default = 2000;
	const int energy_default = energy_max_default;
	const int scout_range = 5;
	const int turns_per_shot = 5;

	class Square: public BasicSquare {
		int durability;

		public:

		Square(std::istream& s);

		void Draw(WINDOW* win, int y, int x) const { mvwaddch(win, y, x, c); }
		void Damage(int damage) { if ((durability -= damage) <= 0) { c = '.'; } }
	};

	class Terrain {
		int height, width;

		std::vector<std::vector<Square> > squares;

		public:

		Terrain(std::istream& s);

		int GetHeight() const { return height; }
		int GetWidth() const { return width; }

		const Square& At(int x, int y) const { return squares[y][x]; }
		Square& At(int x, int y) { return squares[y][x]; }

		void Draw(WINDOW* win) const;

		bool IsObserved(double x1, double y1, double x2, double y2) const;

		Point ChooseSpawnPoint() const;
	};

	class Server;

	class Client {
		friend class Server;

		bool finished;

		protected:

		Server* serv;

		double x, y;
		int vx, vy;
		int ax, ay;

		int energy, energy_max;
		int health;

		bool fire;
		int fire_x, fire_y;
		int fire_damage;
		int fire_damage_max;
		int turns_before_shot;

		public:

		Client(): finished(false), vx(0), vy(0), ax(0), ay(0), energy(energy_default), energy_max(energy_max_default), health(100), fire(false), fire_damage_max(7), turns_before_shot(0) {}
		virtual ~Client() {}

		void Finish() { finished = true; }

		double GetX() const { return x; }
		double GetY() const { return y; }

		bool IsObserved(double ax, double ay) const;
		void ObserveTerrain(std::vector<Point>&) const;
		void ObserveClients(std::vector<Client*>&) const;
		void ObserveTreasures(std::vector<Point>&) const;

		void Draw(WINDOW* win) const { mvwaddch(win, int(y), int(x), '*'); }

		virtual std::string GetName() = 0;
		virtual void Poll() {}
		virtual void Turn() {}
	};

	class Treasure {
		friend class Server;

		int energy_max_bonus;
		int fire_damage_max_bonus;

		public:

		Treasure() { energy_max_bonus = random() % 500 + 1; fire_damage_max_bonus = random() % 5; }
		void Draw(WINDOW* win, int x, int y) { mvwaddch(win, y, x, '+'); }
	};

	class Server {
		const static unsigned ntreasures_max = 10;
		const static int treasure_appearance_probability_denominator = 20;
		const static int treasure_evanescence_probability_denominator = 22;

		WINDOW* field_win;
		WINDOW* log_win;

		Terrain terrain;
		std::vector<Client*> clients;
		std::map<Point, Treasure> treasures;

		void TurnClient(Client* client);
		bool FireClient(Client* client);

		public:

		std::stringstream log;

		Server(std::istream& s);
		virtual ~Server();

		const Terrain& GetTerrain() const { return terrain; }
		const std::vector<Client*>& GetClients() const { return clients; }
		const std::map<Point, Treasure>& GetTreasures() const { return treasures; }

		void AddClient(Client*);
		void FlushClients();
		unsigned GetClientsCount() { return clients.size(); }

		void Draw();

		virtual void Poll();
		void Turn();
	};

	class TelnetClient: public Client {
		Socket* sock;

		std::stringstream query;
		size_t query_size;
		static const size_t query_quota = 2000;

		std::string name;

		void HandleQuery();

		public:

		TelnetClient(Socket* _sock): sock(_sock), query_size(0) { sock->Blocking(false); }
		virtual ~TelnetClient() { delete sock; }

		virtual std::string GetName();
		virtual void Poll();
		virtual void Turn();
	};

	class TelnetServer: public Server {
		static const unsigned nslots = 16;

		Socket lsock;

		public:

		TelnetServer(std::istream& s, unsigned short port = 8888);

		virtual void Poll();
	};
}

robot::Square::Square(std::istream& s) {
	if (!(s >> c)) {
		throw Exception("Can't read square");
	}
	durability = 20;
}

robot::Terrain::Terrain(std::istream& s) {
	if (!(s >> height >> width)) {
		throw Exception("Can't read terrain");
	}

	squares.resize(height);
	for (int y = 0; y < height; ++y) {
		for (int x = 0; x < width; ++x) {
			squares[y].push_back(Square(s));
		}
	}
}

void robot::Terrain::Draw(WINDOW* win) const {
	for (int y = 0; y < height; ++y) {
		for (int x = 0; x < width; ++x) {
			squares[y][x].Draw(win, y, x);
		}
	}
}

bool robot::Terrain::IsObserved(double x1, double y1, double x2, double y2) const {
	if (x1 < 0.0 || x1 >= width || x2 < 0.0 || x2 >= width ||
	    y1 < 0.0 || y1 >= height || y2 < 0.0 || y2 >= height) {
		return false;
	}

	double r = std::sqrt(sqr(x2 - x1) + sqr(y2 - y1));

	if (r < eps) {
		return true;
	}

	double dx = (x2 - x1) / r;
	double dy = (y2 - y1) / r;

	for (double t = 0; t < r; t += 0.5) {
		int x = int (x1 + dx * t);
		int y = int (y1 + dy * t);

		if (x == int(x2) && y == int(y2)) {
			return true;
		}

		if (!squares[y][x].IsTransparent()) {
			return false;
		}
	}

	return true;
}

robot::Point robot::Terrain::ChooseSpawnPoint() const {
	std::vector<Point> points;

	for (int y = 0; y < height; ++y) {
		for (int x = 0; x < width; ++x) {
			if (squares[y][x].IsEnterable() && !squares[y][x].IsKilling()) {
				points.push_back(Point(x, y));
			}
		}
	}
	if (!points.size()) {
		throw Exception("No suitable squares for spawn");
	}
	return points[random() % points.size()];
}

bool robot::Client::IsObserved(double ax, double ay) const {
	double my_x = floor(x) + 0.5;
	double my_y = floor(y) + 0.5;
	ax = floor(ax) + 0.5;
	ay = floor(ay) + 0.5;
	return (sqr(my_x - ax) + sqr(my_y - ay) < sqr(scout_range)) && serv->GetTerrain().IsObserved(my_x, my_y, ax, ay);
}

void robot::Client::ObserveTerrain(std::vector<Point>& points) const {
	points.clear();
	for (int dx = -scout_range; dx <= scout_range; ++dx) {
		for (int dy = -scout_range; dy <= scout_range; ++dy) {
			if (IsObserved(x + dx, y + dy)) {
				points.push_back(Point(int(x + dx), int(y + dy)));
			}
		}
	}
}

void robot::Client::ObserveClients(std::vector<Client*>& aclients) const {
	const std::vector<Client*>& clients = serv->GetClients();

	aclients.clear();
	for (std::vector<Client*>::const_iterator ii = clients.begin(), iiend = clients.end(); ii != iiend; ++ii) {
		if (IsObserved((*ii)->x, (*ii)->y)) {
			aclients.push_back(*ii);
		}
	}
}

void robot::Client::ObserveTreasures(std::vector<Point>& points) const {
	const std::map<Point, Treasure>& treasures = serv->GetTreasures();

	points.clear();
	for (std::map<Point, Treasure>::const_iterator ii = treasures.begin(), iiend = treasures.end(); ii != iiend; ++ii) {
		if (IsObserved(ii->first.x, ii->first.y)) {
			points.push_back(ii->first);
		}
	}
}

robot::Server::Server(std::istream& s): terrain(s) {
	initscr();
	werase(stdscr);
	wrefresh(stdscr);

	int stdscr_width, stdscr_height;
	getmaxyx(stdscr, stdscr_height, stdscr_width);

	field_win = subwin(stdscr, terrain.GetHeight(), terrain.GetWidth(), 0, 0);
	werase(field_win);

	log_win = subwin(stdscr, stdscr_height - terrain.GetHeight() - 1, stdscr_width, terrain.GetHeight() + 1, 0);
	scrollok(log_win, TRUE);
	werase(log_win);
}

robot::Server::~Server() {
	delwin(field_win);
	delwin(log_win);
	delwin(stdscr);
	endwin();
}

void robot::Server::AddClient(Client* client) {
	try {
		Point point = terrain.ChooseSpawnPoint();

		client->x = point.x;
		client->y = point.y;

		client->serv = this;
	} catch (Exception e) {
		log << client->GetName() << " dropped (" << e << ")" << std::endl;
		delete client;
		return;
	}

	log << client->GetName() << " accepted" << std::endl;
	clients.push_back(client);
}

void robot::Server::FlushClients() {
	for (unsigned i = 0; i < clients.size();) {
		if (clients[i]->finished) {
			log << clients[i]->GetName() << " disconnected" << std::endl;
			delete clients[i];
			clients[i] = clients.back();
			clients.pop_back();
		} else {
			++i;
		}
	}
}

void robot::Server::Draw() {
	terrain.Draw(field_win);
	for (std::map<Point, Treasure>::iterator ii = treasures.begin(), iiend = treasures.end(); ii != iiend; ++ii) {
		ii->second.Draw(field_win, ii->first.x, ii->first.y);
	}
	for (std::vector<Client*>::iterator ii = clients.begin(), iiend = clients.end(); ii != iiend; ++ii) {
		(*ii)->Draw(field_win);
	}
	wrefresh(field_win);

	waddstr(log_win, log.str().c_str());
	log.str("");
	wrefresh(log_win);
}

void robot::Server::Poll() {
	for (std::vector<Client*>::iterator ii = clients.begin(), iiend = clients.end(); ii != iiend; ++ii) {
		(*ii)->Poll();
	}
	FlushClients();
}

void robot::Server::Turn() {
	if ((treasures.size() < ntreasures_max) && !(random() % treasure_appearance_probability_denominator)) {
		treasures[terrain.ChooseSpawnPoint()] = Treasure();
	}
	if (treasures.size() && !(random() % treasure_evanescence_probability_denominator)) {
		std::map<Point, Treasure>::iterator ii = treasures.begin();
		for (int i = random() % treasures.size(); i; --i, ++ii);
		treasures.erase(ii);
	}

	for (std::vector<Client*>::iterator ii = clients.begin(), iiend = clients.end(); ii != iiend; ++ii) {
		TurnClient(*ii);
	}
	for (std::vector<Client*>::iterator ii = clients.begin(), iiend = clients.end(); ii != iiend; ++ii) {
		(*ii)->Turn();

		std::map<Point, Treasure>::iterator jj = treasures.find(Point(int((*ii)->x), int((*ii)->y)));
		if (jj != treasures.end()) {
			log << (*ii)->GetName() << " eats bonus :)" << std::endl;
			(*ii)->energy_max += jj->second.energy_max_bonus;
			(*ii)->fire_damage_max += jj->second.fire_damage_max_bonus;
			treasures.erase(jj);
		}
	}

	std::map<Point, Client*> occupation;
	for (std::vector<Client*>::iterator ii = clients.begin(), iiend = clients.end(); ii != iiend; ++ii) {
		Point point(int((*ii)->x), int((*ii)->y));
		std::map<Point, Client*>::iterator jj = occupation.find(point);

		if (jj == occupation.end()) {
			occupation[point] = *ii;
		} else {
			log << (*ii)->GetName() << " smashes into bot " << jj->second->GetName() << std::endl;
			(*ii)->Finish();
			jj->second->Finish();
		}
	}

	FlushClients();
}

void robot::Server::TurnClient(Client* client) {
	int vx = client->vx + client->ax;
	int vy = client->vy + client->ay;

	if (abs(vx) >= nspeeds) { vx = client->vx; }
	if (abs(vy) >= nspeeds) { vy = client->vy; }

	const Square& square = terrain.At(int(client->x), int(client->y));

	if (abs(vx) + abs(vy) > square.GetMaxSpeed()) {	vx = client->vx; vy = client->vy; }
	if (abs(vx) + abs(vy) > square.GetMaxSpeed()) {	vx = vy = 0; }

	client->energy += square.GetEnergyContribution();
	if (client->energy > client->energy_max) { client->energy = client->energy_max;	}

	if (client->energy < (speeds[abs(vx)].energy_use + speeds[abs(vy)].energy_use) * square.GetEnergyFactor()) { vx = client->vx; vy = client->vy; }
	if (client->energy < (speeds[abs(vx)].energy_use + speeds[abs(vy)].energy_use) * square.GetEnergyFactor()) { vx = vy = 0; }

	double x = client->x;
	double y = client->y;

	if (vx >= 0) { x += speeds[vx].velocity; } else { x -= speeds[-vx].velocity; }
	if (vy >= 0) { y += speeds[vy].velocity; } else { y -= speeds[-vy].velocity; }

	client->energy -= (speeds[abs(vx)].energy_use + speeds[abs(vy)].energy_use) * square.GetEnergyFactor();

	if (x < 0.0) { x = 0.0; vx = 0; } else
	if (x >= terrain.GetWidth()) { x = terrain.GetWidth() - eps; vx = 0; }

	if (y < 0.0) { y = 0.0;	vy = 0; } else
	if (y >= terrain.GetHeight()) { y = terrain.GetHeight() - eps; vy = 0; }

	const Square& new_square = terrain.At(int(x), int(y));

	if (!new_square.IsEnterable()) { x = client->x;	y = client->y; vx = vy = 0; }

	client->x = x; client->y = y;
	client->vx = vx; client->vy = vy;
	client->ax = client->ay = 0;

	if (new_square.IsKilling()) {
		log << client->GetName() << " sinks in the river" << std::endl;
		client->Finish();
	}

	if (FireClient(client)) {
		client->turns_before_shot = turns_per_shot;
	} else if (client->turns_before_shot) {
		--client->turns_before_shot;
	}
	client->fire = false;
}

bool robot::Server::FireClient(Client* client) {
	if (!client->fire) {
		return false;
	}
	if (client->turns_before_shot) {
		log << client->GetName() << " tries to fire with non-recharged gun, ignored" << std::endl;
		return false;
	}
	if (!client->IsObserved(client->fire_x, client->fire_y)) {
		log << client->GetName() << " tries to shoot out his visible zone, ignored" << std::endl;
		return false;
	}

	if ((client->vy != 0 || client->vx != 0) && (client->fire_x != int(client->x) || client->fire_y != int(client->y))) {
		double delta = fabs(polar_angle(client->fire_y - client->y, client->fire_x - client->x) - polar_angle(client->vy, client->vx));
		if (delta > M_PI) { delta = 2 * M_PI - delta; }

		if (delta >= 0.25 * M_PI) {
			log << client->GetName() << " tries to shoot out his fire sector, ignored" << std::endl;
			return false;
		}
	}

	if (client->fire_damage < 0) { client->fire_damage = 0; }
	if (client->energy < client->fire_damage) { client->fire_damage = client->energy; }
	client->energy -= client->fire_damage;

	Square& square = terrain.At(client->fire_x, client->fire_y);

	if (!square.IsRefuge()) {
		for (std::vector<Client*>::iterator ii = clients.begin(), iiend = clients.end(); ii != iiend; ++ii) {
			if (int((*ii)->x) == client->fire_x && int((*ii)->y) == client->fire_y) {
				log << client->GetName() << " causes " << client->fire_damage << " damage to " << (*ii)->GetName() << std::endl;

				if (((*ii)->health -= client->fire_damage) <= 0) {
					log << (*ii)->GetName() << " dies. Hoorah!" << std::endl;
					(*ii)->Finish();
				}
				return true;
			}
		}
	}

	if (square.IsDestructable()) {
		log << client->GetName() << " causes " << client->fire_damage << " damage to square at (" << client->fire_x << ", " << client->fire_y << ")" << std::endl;
		square.Damage(client->fire_damage);
		return true;
	}

	log << client->GetName() << " causes " << client->fire_damage << " damage to undestructable square at (" << client->fire_x << ", " << client->fire_y << ")" << std::endl;

	return true;
}

std::string robot::TelnetClient::GetName() {
	if (name.size()) {
		return name;
	}

	int fd = sock->Handle();
	struct sockaddr_in sin;
	socklen_t len = sizeof(sin);

	std::stringstream ss;
	if (getpeername(fd, (struct sockaddr*)&sin, &len) == -1) {
		ss << "sock" << fd << " (" << SocketException() << ")";
	} else {
		ss << "sock" << fd << "(" << inet_ntoa(sin.sin_addr) << ":" << ntohs(sin.sin_port) << ")";
	}

	return ss.str();
}

void robot::TelnetClient::Poll() {
	try {
		char c;

		while (sock->Receive(&c, sizeof(c)) == sizeof(c)) {
			if (c == '\n') {
				HandleQuery();

				query.str("");
				query.clear();
				query_size = 0;

				return;
			}

			if (++query_size > query_quota) {
				Finish();
				return;
			}

			if (c != '\r') {
				query << c;
			}
		}
	} catch (SocketException e) {
		serv->log << GetName() << ": " << e << std::endl;
		Finish();
	}
}

void robot::TelnetClient::HandleQuery() {
	std::string cmd;

//	serv->log << GetName() << " -> " << query.str() << std::endl;

	while (query >> cmd) {
//		serv->log << GetName() << " -> " << cmd << std::endl;
		if (cmd == "q") {
			Finish();
		} else if (cmd == "h") {
			ax = -1;
		} else if (cmd == "j") {
			ay = 1;
		} else if (cmd == "k") {
			ay = -1;
		} else if (cmd == "l") {
			ax = 1;
		} else if (cmd == "s") {
			vx = vy = 0;
		} else if (cmd == "f") {
			fire = (query >> fire_x >> fire_y >> fire_damage);
		} else if (cmd == "name") {
			std::string oldname = GetName();
			if (query >> name) {
				serv->log << oldname << " renames to " << name << std::endl;
			}
		}
	}
}

void robot::TelnetClient::Turn() {
	std::stringstream ss;

	ss << "width " << serv->GetTerrain().GetWidth() << " height " << serv->GetTerrain().GetHeight()
	   << " x " << x << " y " << y << " vx " << vx << " vy " << vy
	   << " energy " << energy << " energy_max " << energy_max << " health " << health
	   << " fire_damage_max " << fire_damage_max << " turns_before_shot " << turns_before_shot << "\r\n";

	ss << "terrain";

	std::vector<Point> points;
	std::vector<Client*> clients;

	ObserveTerrain(points);
	const Terrain& terrain = serv->GetTerrain();
	for (std::vector<Point>::iterator ii = points.begin(), iiend = points.end(); ii != iiend; ++ii) {
		ss << " " << ii->x << " " << ii->y << " " << terrain.At(ii->x, ii->y);
	}
	ss << "\r\n";

	ObserveClients(clients);
	ss << "robots";
	for (std::vector<Client*>::iterator ii = clients.begin(), iiend = clients.end(); ii != iiend; ++ii) {
		if ((*ii)->GetX() != x || (*ii)->GetY() != y) {
			ss << " " << (*ii)->GetX() << " " << (*ii)->GetY();
		}
	}
	ss << "\r\n";

	ObserveTreasures(points);
	ss << "treasures";
	for (std::vector<Point>::iterator ii = points.begin(), iiend = points.end(); ii != iiend; ++ii) {
		ss << " " << ii->x << " " << ii->y;
	}
	ss << "\r\n";

	ss << "turn\r\n";

	try {
		std::string s = ss.str();

		sock->Send(s.c_str(), s.size());
	} catch (SocketException e) {
		serv->log << e << std::endl;
		Finish();
	}
}

robot::TelnetServer::TelnetServer(std::istream& s, unsigned short port):
	Server(s)
{
	lsock.ReuseAddr(true);
	lsock.Blocking(false);
	lsock.Bind(port);
	lsock.Listen();
}

void robot::TelnetServer::Poll() {
	try {
		if (Socket* sock = lsock.Accept()) {
			if (GetClientsCount() < nslots) {
				AddClient(new TelnetClient(sock));
			} else {
				log << "A connection dropped due to the lack of slots" << std::endl;
				delete sock;
			}
		}
	} catch (SocketException e) {
		log << e << std::endl;
	}

	Server::Poll();
}

int main() {
	signal(SIGPIPE, SIG_IGN);

	try {
		std::ifstream s("task1.in");
		robot::TelnetServer serv(s);
		while (1) {
			for (int i = 0; i < 10; ++i) {
				serv.Poll();
				usleep(10000);
			}
			serv.Turn();
			serv.Draw();
		}
	} catch (SocketException e) {
		std::cerr << e << std::endl;
		return 1;
	} catch (robot::Exception e) {
		std::cerr << e << std::endl;
		return 1;
	}

	return 0;
}
